package com.fit.spring.boot.mail.service.impl;

import com.fit.spring.boot.mail.service.MailService;
import com.fit.utils.se.EmptyUtils;
import com.fit.utils.se.IOUtil;
import com.fit.utils.se.IdentitiesUtil;
import com.fit.utils.se.VelocityUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @describe: 邮件服务实现
 * @author: hcl  
 * @date: 2019/3/11 20:50
 */
@Slf4j
@Service
public class MailServiceImpl implements MailService{

    //默认编码
    public static final String DEFAULT_ENCODING = "UTF-8";

    //本身邮件的发送者，来自邮件配置
    @Value("${spring.mail.username}")
    private String userName;

    //邮件发送的对象，用于邮件发送
    @Autowired
    private JavaMailSender mailSender;


    /**
     * @describe: 发邮件基础方法
     * @author: hcl
     * @date: 2019/3/12 13:10
     * @param: [subject, content, targetEail, ccPeoples, bccPeoples, attachments, imageResource, isHtml]
     * @return void
     */
    private void basicSendMail(String subject,String content, List<String> targetEail, List<String> ccPeoples, List<String> bccPeoples, List<AttachmentResource> attachments, List<ImageResource> imageResources,boolean isHtml){

        //检验参数：邮件主题、收件人、邮件内容必须不为空才能够保证基本的逻辑执行
        if(!EmptyUtils.isNotEmptys(subject,targetEail,content)){
            log.error("邮件-> {} 无法继续执行，因为缺少基本的参数：邮件主题、收件人、邮件内容",subject);
            throw new RuntimeException("模板邮件无法继续发送，因为缺少必要的参数！");
        }
        log.debug("开始发送简单文本邮件：主题->{}，收件人->{}，抄送人->{}，密送人->{}",subject,targetEail,ccPeoples,bccPeoples);
        //附件处理，需要处理附件时，需要使用二进制信息，使用 MimeMessage 类来进行处理
        if(!EmptyUtils.isEmptys(attachments,imageResources) || isHtml){
            try{
                //附件和图片处理需要进行二进制传输
                MimeMessage mimeMessage = mailSender.createMimeMessage();
                MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,true,DEFAULT_ENCODING);
                //设置邮件的基本信息
                handleBasicInfo(helper,subject,content,targetEail,ccPeoples,bccPeoples,isHtml);
                if(!EmptyUtils.isEmptys(attachments,imageResources)){
                    //处理图片
                    //内联资源的资源附加，这个必须要放置在设置基本信息的操作后面，或者是全部内容解析完毕后才可以，不能边解析，边占位
                    handleAttachment(helper,subject,imageResources,attachments);
                }
                //发送该邮件
                mailSender.send(mimeMessage);
                log.debug("发送邮件成功: 主题->{}",subject);
            }catch(MessagingException e){
                log.error("发送邮件失败: 主题->{}",subject,e);
                throw new RuntimeException("发送邮件失败！",e);
            }
        }else{
            //创建一个简单邮件信息对象
            SimpleMailMessage simpleMailMessage = new SimpleMailMessage();
            //设置邮件的基本信息
            handleBasicInfo(simpleMailMessage,subject,content,targetEail,ccPeoples,bccPeoples);
            //发送邮件
            mailSender.send(simpleMailMessage);
            log.debug("发送邮件成功: 主题->{}",subject,targetEail,ccPeoples,bccPeoples,attachments);
        }

    }


    /**
     * 处理二进制邮件的基本信息，比如需要带附件的文本邮件、HTML文件、图片邮件、模板邮件等等
     *
     * @param mimeMessageHelper：二进制文件的包装类
     * @param subject：邮件主题
     * @param content：邮件内容
     * @param toWho：收件人
     * @param ccPeoples：抄送人
     * @param bccPeoples：暗送人
     * @param isHtml：是否是HTML文件，用于区分带附件的简单文本邮件和真正的HTML文件
     *
     * @return ：返回这个过程中是否出现异常，当出现异常时会取消邮件的发送
     */
    private void handleBasicInfo(MimeMessageHelper mimeMessageHelper,String subject,String content,List<String> toWho,List<String> ccPeoples,List<String> bccPeoples,boolean isHtml){
        try{
            //设置必要的邮件元素
            //设置发件人
            mimeMessageHelper.setFrom(userName);
            //设置邮件的主题
            mimeMessageHelper.setSubject(subject);
            //设置邮件的内容，区别是否是HTML邮件
            mimeMessageHelper.setText(content,isHtml);
            //设置邮件的收件人
            mimeMessageHelper.setTo(toWho.toArray(new String[toWho.size()]));
            //设置非必要的邮件元素，在使用helper进行封装时，这些数据都不能够为空
            if(EmptyUtils.isNotEmpty(ccPeoples)) {
                //设置邮件的抄送人：MimeMessageHelper # Assert.notNull(cc, "Cc address array must not be null");
                mimeMessageHelper.setCc(ccPeoples.toArray(new String[ccPeoples.size()]));
            }
            if(EmptyUtils.isNotEmpty(bccPeoples)) {
                //设置邮件的密送人：MimeMessageHelper # Assert.notNull(bcc, "Bcc address array must not be null");
                mimeMessageHelper.setBcc(bccPeoples.toArray(new String[bccPeoples.size()]));
            }
        }catch(MessagingException e){
            log.error("邮件基本信息设置出错->{}", subject, e);
            throw new RuntimeException("邮件基本信息设置出错！",e);
        }
    }

    /**
     * 用于填充简单文本邮件的基本信息
     *
     * @param simpleMailMessage：文本邮件信息对象
     * @param subject：邮件主题
     * @param content：邮件内容
     * @param toWho：收件人
     * @param ccPeoples：抄送人
     * @param bccPeoples：暗送人
     */
    private void handleBasicInfo(SimpleMailMessage simpleMailMessage,String subject,String content,List<String> toWho,List<String> ccPeoples,List<String> bccPeoples){
        //设置发件人
        simpleMailMessage.setFrom(userName);
        //设置邮件的主题
        simpleMailMessage.setSubject(subject);
        //设置邮件的内容
        simpleMailMessage.setText(content);
        //设置邮件的收件人
        simpleMailMessage.setTo(toWho.toArray(new String[toWho.size()]));
        if(EmptyUtils.isNotEmpty(ccPeoples)){
            //设置邮件的抄送人
            simpleMailMessage.setCc(ccPeoples.toArray(new String[ccPeoples.size()]));
        }
        if(EmptyUtils.isNotEmpty(bccPeoples)){
            //设置邮件的抄送人
            simpleMailMessage.setBcc(bccPeoples.toArray(new String[bccPeoples.size()]));
        }
    }


    /**
     * 用于处理附件信息，附件需要 MimeMessage 对象
     *
     * @param mimeMessageHelper：处理附件的信息对象
     * @param subject：邮件的主题，用于日志记录
     * @param attachments：图片集合
     * @param attachments：附件集合
     */
    private void handleAttachment(MimeMessageHelper mimeMessageHelper,String subject,List<ImageResource> imageResources,List<AttachmentResource> attachments){
        try{

            if(EmptyUtils.isNotEmpty(imageResources)){
                //循环处理邮件的图片
                for (ImageResource imgResource : imageResources) {
                    //获取资源的id
                    String id = imgResource.id;
                    if(!imgResource.isFileResource){
                        //文件名
                        String fileName = imgResource.imageFileName;
                        //获取输入流
                        InputStream in = imgResource.inputStream;
                        if(EmptyUtils.isNotEmpty(in)){
                            try{
                                //获取该路径所对应的文件资源对象
                                ByteArrayResource resource = new ByteArrayResource(IOUtil.toByte(in));
                                //添加内联资源
                                mimeMessageHelper.addInline(id, resource, mimeMessageHelper.getFileTypeMap().getContentType(fileName));
                            }catch(MessagingException|IOException e){
                                log.error("邮件->{} 添加内联图片 出现异常->{}",subject,e.getMessage(),e);
                                throw new RuntimeException("添加内联图片出错！",e);
                            }
                        }
                    }else{
                        FileSystemResource resource = new FileSystemResource(imgResource.imageFile);
                        try{
                            //添加内联资源
                            mimeMessageHelper.addInline(id,resource);
                        }catch(MessagingException e){
                            log.error("邮件->{} 添加内联图片 出现异常->{}",subject,e.getMessage(),e);
                            throw new RuntimeException("添加内联图片出错！",e);
                        }
                    }
                }
            }
            if(EmptyUtils.isNotEmpty(attachments)){
                for (AttachmentResource attachment : attachments) {
                    if(!attachment.isFileResource){
                        //获取资源的名称
                        String fileName = attachment.fileName;
                        //获取输入流
                        InputStream in = attachment.inputStream;
                        if(EmptyUtils.isNotEmpty(in)){
                            try{
                                //获取该路径所对应的文件资源对象
                                ByteArrayResource resource = new ByteArrayResource(IOUtil.toByte(in));
                                //添加附件
                                mimeMessageHelper.addAttachment(fileName,resource,mimeMessageHelper.getFileTypeMap().getContentType(fileName));
                            }catch(MessagingException|IOException e){
                                log.error("邮件->{} 添加附件 出现异常->{}",subject,e.getMessage(),e);
                                throw new RuntimeException("添加附件出错！",e);
                            }
                        }
                    }else{
                        FileSystemResource resource = new FileSystemResource(attachment.file);
                        try{
                            if(EmptyUtils.isNotEmpty(attachment.fileName)){
                                //添加附件
                                mimeMessageHelper.addAttachment(attachment.fileName,resource,mimeMessageHelper.getFileTypeMap().getContentType(resource.getFilename()));
                            }else{
                                //添加附件
                                mimeMessageHelper.addAttachment(resource.getFilename(),resource);
                            }
                        }catch(MessagingException e){
                            log.error("邮件->{} 添加附件 出现异常->{}",subject,e.getMessage(),e);
                            throw new RuntimeException("添加附件出错！",e);
                        }
                    }
                }
            }
        }finally{
            if(EmptyUtils.isNotEmpty(attachments)){
                for (AttachmentResource attachment : attachments) {
                    if(!attachment.isFileResource){
                        IOUtil.close(attachment.inputStream);
                    }
                }
            }
            if(EmptyUtils.isNotEmpty(imageResources)){
                for (ImageResource resource : imageResources) {
                    if(!resource.isFileResource){
                        IOUtil.close(resource.inputStream);
                    }
                }
            }

        }
    }


    @Override
    public void sendMail(String subject, String content, List<String> targetEail, List<String> ccPeoples, List<String> bccPeoples, List<AttachmentResource> attachments) {
        this.basicSendMail(subject,content,targetEail,ccPeoples,bccPeoples,attachments,null,false);
    }

    @Override
    public void sendHtmlTemplateMail(String subject, List<String> targetEail, List<String> ccPeoples, List<String> bccPeoples, List<AttachmentResource> attachments, Template template, VelocityContext context, List<ImageResource> imageResources) {
        //检验参数：邮件模板必须不为空才能够保证基本的逻辑执行
        if(EmptyUtils.isEmpty(template)){
            log.error("邮件-> {} 无法继续执行，因为缺少基本的参数：邮件模板",subject);
            throw new RuntimeException("模板邮件无法继续发送，因为缺少必要的参数！");
        }
        if(EmptyUtils.isEmpty(context)){
            context =  new VelocityContext();
        }
        String content = VelocityUtil.generateTemplateInstanceToString(context,template);
        this.basicSendMail(subject,content,targetEail,ccPeoples,bccPeoples,attachments,imageResources,true);
    }

    @Override
    public void sendHtmlTemplateMail(String subject, List<String> targetEail, List<String> ccPeoples, List<String> bccPeoples, List<AttachmentResource> attachments, String templateStr, VelocityContext context, List<ImageResource> imageResources) {
        //检验参数：邮件模板必须不为空才能够保证基本的逻辑执行
        if(EmptyUtils.isEmpty(templateStr)){
            log.error("邮件-> {} 无法继续执行，因为缺少基本的参数：邮件模板",subject);
            throw new RuntimeException("模板邮件无法继续发送，因为缺少必要的参数！");
        }
        if(EmptyUtils.isEmpty(context)){
            context =  new VelocityContext();
        }
        String content = VelocityUtil.generateTemplateStrInstanceToString(context,templateStr);
        this.basicSendMail(subject,content,targetEail,ccPeoples,bccPeoples,attachments,imageResources,true);
    }




    /**
     * @describe: 图片资源对象
     * @author: hcl
     * @date: 2019/3/12 20:58
     */
    public static class ImageResource{
        /**
         * @describe: id
         */
        @Getter
        private String name;
        /**
         * @describe: id
         */
        private String id;
        /**
         * @describe: 图片文件名
         */
        private String imageFileName;
        /**
         * @describe: 图片输入流
         */
        private InputStream inputStream;

        /**
         * @describe: 图片文件
         */
        private File imageFile;

        /**
         * @describe: 是否是文件资源
         */
        private boolean isFileResource = false;
        /**
         * @describe: 根据图片文件名称和图片输入流 创建一个图片资源
         * @author: hcl
         * @date: 2019/3/12 23:30
         * @param: [imageFileName, inputStream]
         */
        public ImageResource(String imageFileName,InputStream inputStream){
            if(EmptyUtils.isNotEmptys(imageFileName,inputStream)){
                this.imageFileName = imageFileName;
                this.inputStream = inputStream;
                this.id = IdentitiesUtil.uuid();
                this.name = "cid:"+id;
            }else{
                throw new RuntimeException("创建图片资源对象失败，缺少图片名称或输入流！");
            }
        }

        /**
         * @describe: 根据图片文件创建一个图片资源
         * @author: hcl
         * @date: 2019/3/12 23:31
         * @param: [imageFile]
         */
        public ImageResource(File imageFile){
            if(EmptyUtils.isNotEmpty(imageFile) && imageFile.exists() && imageFile.isFile()){
                this.imageFile = imageFile;
                this.id = IdentitiesUtil.uuid();
                this.name = "cid:"+id;
                isFileResource = true;
            }else{
                throw new RuntimeException("创建图片资源对象失败，图片文件不存在！");
            }
        }
    }


    /**
     * @describe: 附件资源
     * @author: hcl  
     * @date: 2019/3/13 0:01
     */
    public static class AttachmentResource{

        /**
         * @describe: 附件文件名
         */
        private String fileName;
        /**
         * @describe: 附件输入流
         */
        private InputStream inputStream;

        /**
         * @describe: 文件
         */
        private File file;

        /**
         * @describe: 是否是文件资源
         */
        private boolean isFileResource = false;


        /**
         * @describe: 通过文件名和输入流创建一个附件资源对象
         * @author: hcl
         * @date: 2019/3/13 0:05
         * @param: [fileName, inputStream]
         */
        public AttachmentResource(String fileName,InputStream inputStream){
            if(EmptyUtils.isNotEmptys(fileName,inputStream)){
                this.fileName = fileName;
                this.inputStream = inputStream;
            }else{
                throw new RuntimeException("创建附件资源对象失败，缺少名称或输入流！");
            }
        }

        /**
         * @describe: 根据文件创建一个附件资源对象
         * @author: hcl
         * @date: 2019/3/13 0:05
         * @param: [imageFile]
         */
        public AttachmentResource(File file){
            if(EmptyUtils.isNotEmpty(file) && file.exists() && file.isFile()){
                this.file = file;
                isFileResource = true;
            }else{
                throw new RuntimeException("创建图片资源对象失败，图片文件不存在！");
            }
        }

        /**
         * @describe: 根据文件名和文件创建一个附件资源对象
         * @author: hcl
         * @date: 2019/3/13 0:05
         * @param: [fileName,file]
         */
        public AttachmentResource(String fileName,File file){
            if(EmptyUtils.isNotEmpty(file) && file.exists() && file.isFile()){
                this.file = file;
                isFileResource = true;
            }else{
                throw new RuntimeException("创建图片资源对象失败，图片文件不存在！");
            }
            this.fileName = fileName;
        }
    }

}
